# coding=utf8

import numpy as np
from collections import Counter


class Preliminary:


    @staticmethod
    def demo_isna():
        """
        numpy.isnan用于检测多维数组中的缺失值，包括np.NAN，np.NaN, np.nan
        注意有些情况的处理：
        1）np.isnan不将np.inf,np.NZERO等特殊值视为缺失值, 但是在nan_to_num函数中可以单独进行处理
        2）不支持pandas.NA的判断: 在数组中存放pd.NA值后，数组类型一般为object，isnan的判断需要浮点类型
          如，np.isnan(array([pd.NA]) 触发异常：
          TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to
          any supported types according to the casting rule ''safe''
        """
        a = np.array([np.inf, np.NZERO, np.nan, 128])
        b = np.zeros(a.shape)
        print(
            "# 带有无穷、负零、NaN值的数组\n"
            ">>> a = np.array([np.inf, np.NZERO, np.nan, -128])\n"
            f"{a}\n\n"
            f"# 值np.inf不被视为缺失值\n"
            ">>> np.isnan(a)\n"
            f"{np.isnan(a)}\n\n"
            f"# 输出检测结果到数组b\n"
            f">>> b = np.zeros(a.shape)\n"
            f">>> np.isnan(a, out=b)\n"
            f"{np.isnan(a, out=b)}\n"
            f">>> b\n"
            f"{b}\n\n"
            f"# 只有设置True的位置检测NaN值\n"
            f">>> np.isnan(a, where=[True, True, False, True])\n"
            f"{np.isnan(a, where=[True, True, False, True])}"
        )

    @staticmethod
    def isnan_fill_nan():
        print("按行将缺失值置为均值")
        a = np.array([[10, np.nan, 20], [np.nan, 30, 40], [50, 60, 70]])
        print(">>> a")
        print(f"{a}")
        for row in range(a.shape[1]):
            a[row, np.isnan(a[row, :])] = np.nanmean(a[row, :])
        print(
            ">>> for row in range(a.shape[0]):\n"
            "...     a[row, np.isnan(a[row, :])] = np.nanmean(a[row, :])\n"
            ">>> a\n"
            f"{a}"
        )

        print("按列将缺失值置为均值")
        a = np.array([[10, np.nan, 20], [np.nan, 30, 40], [50, 60, 70]])
        print(">>> a")
        print(f"{a}")
        for col in range(a.shape[1]):
            a[np.isnan(a[:, col]), col] = np.nanmean(a[:, col])
        print(
            ">>> for col in range(a.shape[1]):\n"
            "...     a[np.isnan(a[:, col]), col] = np.nanmean(a[:, col])\n"
            ">>> a\n"
            f"{a}"
        )

    @staticmethod
    def nan_to_num():
        x = np.array([np.inf, -np.inf, np.nan, -128, 128])
        print(
            ">>> x\n"
            f"{x}\n"
            ">>> np.nan_to_num(x)\n"
            f"{np.nan_to_num(x)}\n"
            f">>> np.nan_to_num(x, nan=999, posinf=333, neginf=666)\n"
            f"{np.nan_to_num(x, nan=999, posinf=333, neginf=666)}\n"
        )
        a = np.array([1, 5, 2, np.nan, 3])
        print(
            ">>> a = np.array([1, 5, 2, np.nan, 3]\n"
            f"{a}\n"
            f"# 使用均值填充缺失值\n"
            f">>> np.nan_to_num(a, nan=np.nanmean(a))\n"
            f"{np.nan_to_num(a, nan=np.nanmean(a))}\n"
            f"# 使用中位数"
            f">>> np.nan_to_num(a, nan=np.nanmedian(a))\n"
            f"{np.nan_to_num(a, nan=np.nanmedian(a))}\n"
        )

    @staticmethod
    def mode1(x):
        """使用bincount和argmax求整数类型多维数组的众数"""
        if len(x.shape) > 1:
            x = x.flat
        if any(np.isnan(x)):
            x = [v for v in x if not np.isnan(v)]
        return np.argmax(np.bincount(x))

    @staticmethod
    def mode2(x, na=False, allna=0):
        """
        使用Counter求各种数据类型多维数组的众数
        na=True时，考虑存在缺失值情况，去除nan值进行计算
        如果全部为缺失值，返回allna
        """
        if len(x.shape) > 1:
            x = list(x.flat)
        xcounter = Counter(x)
        if na:
            for xc in xcounter.most_common(len(xcounter)):
                if not np.isnan(xc[0]):
                    return xc[0]
            return allna
        return xcounter.most_common(1)[0][0]


def task(cls):
    x = np.genfromtxt("stu91.csv", delimiter=',')
    print(
        ">>> x = np.genfromtxt('stu91.csv', delimiter=',')\n"
        ">>> x\n"
        f"{x}\n"
    )
    for col in range(x.shape[1]):
        x[np.isnan(x[:, col]), col] = np.nanmean(x[:, col])
    print(
        ">>> for col in range(x.shape[1]):\n"
        "...     x[np.isnan(x[:, col]), col] = np.nanmean(x[:, col])\n"
        ">>> x\n"
        f"{x}"
    )


def expand_1():
    x = np.genfromtxt("ch9/stu91.csv", delimiter=',')
    print(
        ">>> x = np.genfromtxt('stu91.csv', delimiter=',')\n"
        ">>> x\n"
        f"{x}\n"
    )
    for col in range(x.shape[1]):
        # fill_value = np.nanmean(x[:, col])
        np.nan_to_num(x[:, col], nan=Preliminary.mode2(x[:, col], na=True), copy=False)
    print(
        ">>> for col in range(x.shape[1]):\n"
        "...     np.nan_to_num(x[:, col], nan=Preliminary.mode2(x[:, col], na=True), copy=False)\n"
        ">>> x\n"
        f"{x}"
    )


if __name__ == "__main__":
    Preliminary.demo_isna()
    Preliminary.isnan_inf()
    Preliminary.nan_to_num()
    a = np.random.randint(1, 3, (3, 3))
    print(Preliminary.mode1(a))
    a = np.array(list('abcdccd'))
    print(Preliminary.mode2(a))
    task()
    expand_1()
